/* 
 * Copyright (c) 2004-2005 Massachusetts Institute of Technology. This code was
 * developed as part of the Haystack (http://haystack.lcs.mit.edu/) research 
 * project at MIT. Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including without 
 * limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions: 
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. 
 */
/*
 * Created on Mar 3, 2003
 *
 */
package edu.mit.csail.relo.jdt;

import java.net.URL;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.PredicateUtils;
import org.apache.commons.collections.Transformer;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.ui.ISharedImages;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.sesame.sail.StatementIterator;

import edu.mit.csail.relo.ReloCore;
import edu.mit.csail.relo.intro.preferences.PreferenceConstants;
import edu.mit.csail.relo.jdt.extractors.MethodParametersExtractor;
import edu.mit.csail.relo.modelBridge.Artifact;
import edu.mit.csail.relo.store.ReloRdfRepository;
import edu.mit.csail.relo.store.StoreUnionRepository;
import edu.mit.csail.relo.store.StoreUtil;
import edu.mit.csail.relo.ui.ReloEditor;
import edu.mit.csail.relo.ui.ReloImageDecorator;

/**
 * @author vineet
 * 
 * Consists of mostly static methods that should only be called when known to
 *  have an object of CodeUnit type (eventually may move to use Jena Resource
 *  heirarchy)
 */
public class CodeUnit extends Artifact {

    static final Logger logger = ReloJDTPlugin.getLogger(CodeUnit.class);


    public CodeUnit(Resource _elementRes) {
        super(_elementRes);
    }


    public CodeUnit(Artifact art) {
	    super(art.elementRes);
	}
	
	public IJavaElement getJDTElement(ReloRdfRepository repo) {
	    return RJCore.resourceToElement(repo, elementRes);
	}

	public static CodeUnit getCodeUnit(ReloRdfRepository repo, IJavaElement element) {
		return new CodeUnit( RJCore.elementToResource(repo, element) );
	}
	
	
	public String getTypeStr(ReloRdfRepository repo) {
        return repo.getRequiredProperty(getType(repo), ReloRdfRepository.rdfsLabel).getObject().toString();
	}

	public static boolean isJavaElementType(ReloRdfRepository repo, Resource typeRes) {
	    return (typeRes.equals(RJCore.classType)
	            || typeRes.equals(RJCore.interfaceType)
	            || typeRes.equals(RJCore.fieldType)
	            || typeRes.equals(RJCore.methodType)
	            || typeRes.equals(RJCore.packageType) );
	}
	public boolean isType(ReloRdfRepository repo) {
	    return isType(repo, getType(repo));
	}

	public static boolean isType(ReloRdfRepository repo, Resource typeRes) {
	    return (RJCore.classType.equals(typeRes) || RJCore.interfaceType.equals(typeRes));
	}

	public boolean isField(ReloRdfRepository repo) {
	    return isField(repo, getType(repo));
	}

	public static boolean isField(ReloRdfRepository repo, Resource typeRes) {
	    return typeRes.equals(RJCore.fieldType);
	}

	public boolean isMethod(ReloRdfRepository repo) {
	    return isMethod(repo, getType(repo));
	}

	public static boolean isMethod(ReloRdfRepository repo, Resource typeRes) {
	    return typeRes.equals(RJCore.methodType);
	}

	public boolean isPackage(ReloRdfRepository repo) {
	    return isPackage(repo, getType(repo));
	}

	public static boolean isPackage(ReloRdfRepository repo, Resource typeRes) {
	    return typeRes.equals(RJCore.packageType);
	}

	public static boolean isJavaProject(ReloRdfRepository repo, Resource typeRes) {
	    return typeRes.equals(RJCore.projectType);
	}
    public static int getContextDepth(ReloRdfRepository repo, Artifact art) {
        PreferenceConstants.loadPrefs();
        Resource artType = art.getType(repo);
        Artifact parentArt = art.getParentArtifact(repo);
        int myDepth = 1;
        
        if (isJavaProject(repo, artType)) {
            myDepth = 0;
        } else  if (isPackage(repo, artType)) {
            myDepth += getCount(getLabel(repo, art, null, true), ".", 0);
        }

        if (parentArt == null)
            return myDepth;
        else
            return myDepth + getContextDepth(repo, parentArt);
    }
    private static int getCount(String lbl, String findStr, int fromNdx) {
        int ndx = lbl.indexOf(findStr, fromNdx);
        if (ndx == -1) 
            return 0;
        else
            return 1 + getCount(lbl, findStr, ndx + 1);
    }


    public int getContextDepth(ReloRdfRepository repo) {
        return CodeUnit.getContextDepth(repo, this);
    }

	public static String getLabel(ReloRdfRepository repo, Artifact art, Artifact contextArt, boolean labelWithContext) {
		if (art.elementRes == null) {
			return "{null}";
		}
		
		String retVal = null;
		Object obj = repo.getStatement(art.elementRes, ReloCore.name, null).getObject();
		if(obj == null) {
			// In the case of class files
			retVal = art.getNonDerivedBaseArtifact().toString();
			retVal = retVal.substring(retVal.lastIndexOf('#') + 1);
			return retVal;
		} else {
			retVal = obj.toString();
		}

		// add contextual information
		if (labelWithContext) {
			Artifact curElemArt = art.getParentArtifact(repo);
			while (curElemArt != null && !curElemArt.equals(contextArt)) {
			    Resource curElemTypeRes = curElemArt.getType(repo);
				if (CodeUnit.isType(repo, curElemTypeRes) || CodeUnit.isPackage(repo, curElemTypeRes)) {
					retVal = curElemArt.getName(repo) + "." + retVal;
				}
				curElemArt = curElemArt.getParentArtifact(repo);
			}
		} else {
			// for packages lets strip out everything in the beginning
			if (isPackage(repo, art.getType(repo))) {
				retVal = retVal.substring(retVal.lastIndexOf(".")+1);
			}
		}
		
		
        if (isField(repo, art.getType(repo))) {
            retVal += ": " + getLabel(repo, new CodeUnit((Resource) repo.getStatement(art.elementRes, RJCore.refType, null).getObject()), contextArt);
        }
		if (isMethod(repo, art.getType(repo))) {
            retVal += MethodParametersExtractor.getInOutSig(art, repo, PreferenceConstants.showMethodReturnTypes);
		}

		return retVal + art.getTrailer();
	}
    public static String getLabel(ReloRdfRepository repo, Artifact art, Artifact contextArt) {
        PreferenceConstants.loadPrefs();
        return getLabel(repo, art, contextArt, PreferenceConstants.labelItemsWithContext);
    }

	public String getLabel(ReloRdfRepository repo, CodeUnit contextCU) {
		return getLabel(repo, this, contextCU);
	}

    public String getLabel(ReloRdfRepository repo) {
        return getLabel(repo, null);
    }

    /**
     * Note the input collection is changed by the method
     */
    @SuppressWarnings("unchecked")
    protected static List<CodeUnit> transformResourcesToCodeUnits(List<Resource> in) {
        CollectionUtils.transform(in, new Transformer() {
            public Object transform(Object arg0) {
                return new CodeUnit((Resource)arg0);
            }});
        return (List<CodeUnit>) (List) in;
    }
    

	public List<CodeUnit> listFwdCU(ReloRdfRepository repo, URI relationRes, Predicate filter) {
		List<Resource> retValCU = new LinkedList<Resource>();
		repo.getResourcesFor(retValCU, elementRes, relationRes, null);
		CollectionUtils.filter(retValCU, filter);
		return transformResourcesToCodeUnits(retValCU);
	}
	
	public List<CodeUnit> listRevCU(ReloRdfRepository repo, URI relationRes, Predicate filter) {
		List<Resource> retValCU = new LinkedList<Resource>();
		repo.getResourcesFor(retValCU, null, relationRes, elementRes);
		CollectionUtils.filter(retValCU, filter);
		return transformResourcesToCodeUnits(retValCU);
	}
	
	public List<CodeUnit> listFwdCU(ReloRdfRepository repo, URI relationRes) {
		return listFwdCU(repo, relationRes, null);
	}

	public List<CodeUnit> listRevCU(ReloRdfRepository repo, URI relationRes) {
		return listRevCU(repo, relationRes, null);
	}

	public List<CodeUnit> listSupertypesCU(ReloRdfRepository repo) {
	    return listFwdCU(repo, RJCore.inherits);
	}

	public List<CodeUnit> listImplementedCU(ReloRdfRepository repo) {
	    return listFwdCU(repo, RJCore.inherits, getTypeFilter(repo, RJCore.interfaceType));
	}

	public CodeUnit getExtendedTypeCU(ReloRdfRepository repo) {
	    return listFwdCU(repo, RJCore.inherits, getTypeFilter(repo, RJCore.classType)).get(0);
	}


	public ReloRdfRepository getSupertypesHierarchyCU(ReloRdfRepository repo) {
	    ReloRdfRepository superTypesModel = StoreUtil.getMemRepository();
		StoreUtil.getObjectsRecursively(superTypesModel, elementRes, RJCore.inherits);
		return superTypesModel;
	}


	
	public static Resource getAccess(ReloRdfRepository repo, Artifact art) {
	    return (Resource) repo.getStatement(art.elementRes, RJCore.access, null).getObject();
	}

	public static ISharedImages isi = JavaUI.getSharedImages();
	public Image getIcon(ReloRdfRepository repo) {
	    return getIcon(repo, this, getType(repo));
	}
	protected static String getIconKey(ReloRdfRepository repo, Artifact art, Resource typeRes) {
	    String iconKey = null;
	    if (typeRes.equals(RJCore.packageType)) {
	        iconKey = ISharedImages.IMG_OBJS_PACKAGE;
	    } else  if (typeRes.equals(RJCore.classType)) {
	        iconKey = ISharedImages.IMG_OBJS_CLASS;
	    } else  if (typeRes.equals(RJCore.interfaceType)) {
	        iconKey = ISharedImages.IMG_OBJS_INTERFACE;
	    } else if (typeRes.equals(RJCore.methodType)) {
		    Resource cuAccess = getAccess(repo, art);
		    if (cuAccess.equals(RJCore.publicAccess))
		        iconKey = ISharedImages.IMG_OBJS_PUBLIC;
		    else if (cuAccess.equals(RJCore.protectedAccess))
		        iconKey = ISharedImages.IMG_OBJS_PROTECTED;
		    else if (cuAccess.equals(RJCore.privateAccess))
		        iconKey = ISharedImages.IMG_OBJS_PRIVATE;
		    else
		        iconKey = ISharedImages.IMG_OBJS_DEFAULT;
	    } else if (typeRes.equals(RJCore.fieldType)) {
		    Resource cuAccess = getAccess(repo, art);
		    if (cuAccess.equals(RJCore.publicAccess))
		        iconKey = ISharedImages.IMG_FIELD_PUBLIC;
		    else if (cuAccess.equals(RJCore.protectedAccess))
		        iconKey = ISharedImages.IMG_FIELD_PROTECTED;
		    else if (cuAccess.equals(RJCore.privateAccess))
		        iconKey = ISharedImages.IMG_FIELD_PRIVATE;
		    else
		        iconKey = ISharedImages.IMG_FIELD_DEFAULT;
	    } else {
	        logger.error("Icon requested for unknown type: " + typeRes, new Exception());
	    }
	    return iconKey;
	}
	public static Image getIcon(ReloRdfRepository repo, Artifact art, Resource typeRes) {
	    String iconKey = getIconKey(repo, art, typeRes);
	    if (iconKey != null) {
	    	Image iconImage = isi.getImage(iconKey);
	    	if(repo instanceof StoreUnionRepository) {
	    		StoreUnionRepository unionRepo = (StoreUnionRepository)repo;
	    		repo = unionRepo.getStoreRepo();
	    	}
	    	//If the part is outdated, decorate it with an error image in the diagram
	    	StatementIterator it = repo.getStatements(art.elementRes, null, null);
			if (!it.hasNext()) {
				ImageDescriptor errorImage = ImageDescriptor.createFromFile(ReloEditor.class, "error_co.gif");
				ReloImageDecorator decorator = new ReloImageDecorator(ImageDescriptor.createFromImage(iconImage), errorImage, ReloImageDecorator.BOTTOM_RIGHT);
				return decorator.createImage();
			}
	    	return iconImage;
	    } else {
	        logger.error("Icon requested for unknown type: " + typeRes);
	        return null;
	    }
	}
	public static ImageDescriptor getIconDescriptor(ReloRdfRepository repo, Artifact art, Resource typeRes) {
	    String iconKey = getIconKey(repo, art, typeRes);
	    if (iconKey != null) {
	        // isi.getImageDescriptor does not support some of the icons (like fields)
	        //  so we use internal methods
	    	int NAME_PREFIX_LENGTH= "org.eclipse.jdt.ui.".length();
	    	String bundleEntry = "/icons/full/obj16/" + iconKey.substring(NAME_PREFIX_LENGTH);
		    URL iconFileURL = Platform.getBundle("org.eclipse.jdt.ui").getEntry(bundleEntry);
            return ImageDescriptor.createFromURL(iconFileURL);
	    } else {
	        logger.error("IconDes requested for unknown type: " + typeRes);
	        return null;
	    }
	}

	public boolean isPublic(ReloRdfRepository repo) {
	    return repo.hasStatement(elementRes, RJCore.access, RJCore.publicAccess);
	}
	
	public List<Artifact> getMethods(ReloRdfRepository repo) {
	    return getChildrenArtifacts(repo, PredicateUtils.andPredicate(
	            getTypeFilter(repo, RJCore.methodType),
	            getAccessFilter(repo, RJCore.publicAccess)
	            ));
	}

	public List<Artifact> getMethods(ReloRdfRepository repo, Resource access) {
	    return getChildrenArtifacts(repo, PredicateUtils.andPredicate(
	            getTypeFilter(repo, RJCore.methodType),
	            getAccessFilter(repo, access)
	            ));
	}
	
	public List<Artifact> getFields(ReloRdfRepository repo) {
	    return getChildrenArtifacts(repo, PredicateUtils.andPredicate(
	            getTypeFilter(repo, RJCore.fieldType),
	            getAccessFilter(repo, RJCore.publicAccess)
	            ));
	}

	public List<Artifact> getFields(ReloRdfRepository repo, Resource access) {
	    return getChildrenArtifacts(repo, PredicateUtils.andPredicate(
	            getTypeFilter(repo, RJCore.fieldType),
	            getAccessFilter(repo, access)
	            ));
	}
	
	public static final Predicate getAccessFilter(ReloRdfRepository repo, Resource access) {
	    return StoreUtil.filterSubjectResPred(repo, RJCore.access, access);
	}
	
	public static final Predicate getTypeFilter(ReloRdfRepository repo, Resource typeRes) {
	    return StoreUtil.filterSubjectResPred(repo, repo.rdfType, typeRes);
	}
	

}